package com.apimarket.service;

import com.alibaba.druid.util.StringUtils;
import com.apimarket.annotation.MultiDSTransactionalAnnotation;
import com.apimarket.dao.ApiInterfaceMapper;
import com.apimarket.dao.ApiSetMapper;
import com.apimarket.dao.ApplyMapper;
import com.apimarket.entity.ApiInterfaceWithBLOBs;
import com.apimarket.entity.ApiSet;
import com.apimarket.entity.example.ApiInterfaceExample;
import com.apimarket.entity.example.ApiSetExample;
import com.apimarket.entity.kong.Plugin;
import com.apimarket.entity.kong.Route;
import com.apimarket.entity.vo.ApiInterfaceWithBOLOBsPath;
import com.apimarket.entity.vo.ApiSetInterfaces;
import com.apimarket.enums.ExceptionEnum;
import com.apimarket.exception.AMException;
import com.apimarket.exception.NetContextException;
import com.apimarket.util.FileUtil;
import com.apimarket.util.RedisUtils;
import com.apimarket.util.kong.KongPlugin;
import com.apimarket.util.kong.KongRoute;
import com.apimarket.util.kong.KongService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.*;

/**
 * api集与api接口服务层
 *
 * @author yyc
 */
@Service
public class ApiService {
    public static final int pageSize = 10;
    Logger logger= LoggerFactory.getLogger(ApiService.class);

    @Autowired
    private ApiInterfaceMapper apiInterfaceMapper;

    @Autowired
    private ApiSetMapper apiSetMapper;

    @Autowired
    private KongService kongService;

    //BaseKongRoute有@Component注解，所以能够依赖注入
    @Autowired
    private KongRoute kongRoute;
    @Autowired
    private ApplyMapper applyMapper;
    @Autowired
    private KongPlugin kongPlugin;

    @Value("${file.save}")
    private String fileSavePath;

    @Value("${applyFile.save}")
    private String applyFileSavePath;

    @Value("${redis.service.key}")
    private String redisServiceKey;
    @Value("${redis.route.key}")
    private String redisRoute;
    @Value("${spring.kong.net}")
    private String kongNet;

    @Autowired
    private RedisUtils redisUtils;

    public int insertRecord(ApiInterfaceWithBLOBs record) {
        return apiInterfaceMapper.insert(record);
    }

    public int insertRecord(ApiSet record) {
        return apiSetMapper.insert(record);
    }

    //将接口集合文件都插入到数据库
    public void insertRecordDisposableAndFile(ApiSet record, MultipartFile imageFile) throws IOException, URISyntaxException {
        if(imageFile.getSize()==0){
            throw new AMException(ExceptionEnum.File_NOT_UPLOAD);
        }
        //1.将文件存储到指定位置
        String picUrl = SaveFile(imageFile);
        //设置这个接口集的图片的存储地址和图片的原名称
        record.setPicUrl(picUrl);
        record.setOrignName(imageFile.getOriginalFilename());
        //将这个接口集信息提交到kong数据库和业务数据库
        insertRecordDisposable(record);
        //需要将缓存中的数据删除
        if(redisUtils.hasKey(redisServiceKey)){
            //如果缓存中有存的话才删除
            redisUtils.del(redisServiceKey);
        }
    }

    /**
     * 每创建一个新的service，需要默认给这个service绑定上acl和key-auth插件
     * 同时生成一个属于这个service的组名
     * 对这个数据集申请通过的用户将添加一个这个组名
     * 从而实现对这个service的访问权
     *
     * @param id 传service的id
     */
    public void bindAclAndKeyAuth(String id) throws IOException, URISyntaxException {
        /**默认开启acl，key-auth插件*/
        Map<String, Object> config = new HashMap<>();
        //RandomStringUtils.randomAlphabetic产生10位长度的随机字符串
        //应该是一开始给路由添加acl插件的时候，随便生成一个允许访问的角色
        config.put("allow", Collections.singletonList(RandomStringUtils.randomAlphabetic(10)));
        //都是用内部类
        Plugin acl = new Plugin.Builder("acl")
                .setService(new Plugin.Service(id))
                .setConfig(config).builder();
        Plugin keyAuth = new Plugin.Builder("key-auth")
                .setService(new Plugin.Service(id)).builder();
        kongPlugin.addPlugin(acl);
        kongPlugin.addPlugin(keyAuth);
    }

    public Plugin bindKeyAuth(String id) throws IOException, URISyntaxException {
        Plugin keyAUth=new Plugin.Builder("key-auth").setService(new Plugin.Service(id)).builder();
        Plugin plugin=kongPlugin.addPlugin(keyAUth);
        return plugin;
    }


    /**
     * 为service绑定acl差劲啊
     * @param id
     */
    public Plugin bindAcl(String id) throws IOException, URISyntaxException {

        Map<String, Object> config = new HashMap<>();
        //RandomStringUtils.randomAlphabetic产生10位长度的随机字符串
        //应该是一开始给路由添加acl插件的时候，随便生成一个允许访问的角色
        config.put("allow", Collections.singletonList(RandomStringUtils.randomAlphabetic(10)));
        //都是用内部类
        Plugin acl = new Plugin.Builder("acl")
                .setService(new Plugin.Service(id))
                .setConfig(config).builder();
        Plugin plugin = kongPlugin.addPlugin(acl);
        return plugin;
    }
    /**
     * 存储文件到指定目录
     * @param imageFile
     * @return 最终的文件存储地址
     */
    public String SaveFile(MultipartFile imageFile){
        //图片类型
        String contentType=imageFile.getContentType();
        //生成一个新的文件名，带后缀
        String fileName = FileUtil.getFileName(imageFile.getOriginalFilename());
        //获取文件存储的路径
        try {
            //存储文件到指定地址
            FileUtil.uploadFile(imageFile.getBytes(),fileSavePath,fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //将文件存储的最终地址返回
        return fileSavePath+fileName;
    }

    /**
     * 存储申请文件到指定目录
     * @param imageFile
     * @return 最终的文件存储地址
     */
    public String SaveApplyFile(MultipartFile imageFile){
        //申请文件存储地址

        //图片类型
        String contentType=imageFile.getContentType();
        //生成一个新的文件名，带后缀
        String fileName = FileUtil.getFileName(imageFile.getOriginalFilename());
        //获取文件存储的路径
        try {
            //存储文件到指定地址
            FileUtil.uploadFile(imageFile.getBytes(),applyFileSavePath,fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //将文件存储的最终地址返回
        return applyFileSavePath+fileName;
    }

    /**
     * 先提交至本业务数据库，
     * 再经由Kong交互对象，添加至Kong的数据库
     * 无论哪一步发生异常，都将保证数据库一致性
     */
    //@Transactional(rollbackFor = {NetContextException.class, IOException.class, URISyntaxException.class})
    //@Transactional(transactionManager = "xaTransaction",rollbackFor = {Exception.class})
    //@Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED
            //, rollbackFor = { Exception.class })

    public void insertRecordDisposable(ApiSet record) throws IOException, URISyntaxException {
        //这里是自己指定生成的service id，并不由kong来生成
        String id = UUID.randomUUID().toString();
        record.setId(id);
        record.setDate(new Date());
        //这里是往自己的业务逻辑数据库插入service信息
        apiSetMapper.insert(record);
        //这里的url是service中要代理的统一url前缀
        //这里保证业务数据库中的api_set和kong数据库中的serviceId相同
        //这里我把添加kong时候的name传参转为拼音了（每个中文拼音的首字母大写组成），但是后面没用到
        kongService.addService(record.getUrl(), record.getName(), record.getId());
        //这里需要为新创建的service绑定两个插件（acl,key-auth）,同时需要为他创建一个属于这个service的组
        bindAclAndKeyAuth(id);
        //int i=1/0;
    }

    /**
     * 流程与提交一致
     * 先删业务数据库再调用api删kong数据库的数据
     * 发生异常均会回滚
     */

    public void deleteApiSetRecordDisposable(String id) throws IOException, URISyntaxException {
        //删除接口集需要先判断这个接口集有没有子Route
        List<Route> routes = kongService.listRoutesByServiceId(id);
        if(routes.size()>0){
            throw new AMException(ExceptionEnum.Del_SERVICE_HAS_ROUTES);
        }
        apiSetMapper.deleteByPrimaryKey(id);
        kongService.deleteService(id);
        //删除后也需要把缓存中的接口集给删除
        //需要将缓存中的数据删除
        redisUtils.judgeAndRemove(redisServiceKey);
        //删除该api集下的所有接口
        apiInterfaceMapper.deleteByServiceId(id);
        //删除所有对这个service的申请
        applyMapper.deleteByServiceId(id);
    }

    /**
     * api接口信息同步至两数据库
     * 需要注意kong中Service中的path属性必须以 / 结尾，Route的path属性，不能以 / 结尾
     * 且Route的pathHandling属性必须为v1，此处已经Route类在构造函数做了默认处理。否则可能无法正确被Kong代理
     * <p>
     * 此处Kong数据库的url需要由业务数据的url提取获得
     * <p>
     * 此处还有个bug逮解决，就是映射path（即Route的path属性）的唯一性约束问题
     *
     * @see com.apimarket.entity.kong.Route
     */
    @Transactional(rollbackFor = {NetContextException.class, IOException.class, URISyntaxException.class})
    public void insertRecordDisposable(ApiInterfaceWithBLOBs record) throws IOException, URISyntaxException {
        String id = UUID.randomUUID().toString();
        record.setId(id);
        //往业务数据库插入
        apiInterfaceMapper.insert(record);
        StringBuilder target = new StringBuilder("/");

        //这一步应该是为了解析出要代理的路由
        //解析出要代理的路由这个方法可以单独提出来，省的以后改找不到
        //目前应该是只考虑了http和https这两种协议
        String url = record.getUrl();
        //回指定字符在字符串中第一次出现处的索引，如果此字符串中没有这样的字符，则返回 -1
        int doubleIndex = url.indexOf("//");
        //public int indexOf(int ch, int fromIndex):
        // 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引，
        // 如果此字符串中没有这样的字符，则返回 -1
        int thirdIndex = url.indexOf("/", doubleIndex + 2);
        int fourthIndex = url.indexOf("/", thirdIndex + 1);
        /**http:www.example.com/hhhh 与 http://www.example.com/hhhh/worker 的区别*/
        fourthIndex = fourthIndex == -1 ? url.length() : fourthIndex;
        target.append(url, thirdIndex + 1, fourthIndex);


        /**
         * 提取必要信息提交借由api提交至Kong的数据库
         * 不提取name字段，因为Kong不支持中文
         */
        Route route = new Route();
        route.setId(record.getId());
        //Collections.singletonList这个方法主要用于只有一个元素的优化，减少内存分配，无需分配额外的内存
        route.setPaths(Collections.singletonList(target.toString()));
        //内部类service创建
        route.setService(new Route.Service(record.getServiceId()));
        List<String> method = Arrays.asList(record.getMethod().concat(",OPTIONS").split(","));
        route.setMethods(method);
        route = kongRoute.addRoute(route);

        /**默认开启acl，key-auth插件*/
        Map<String, Object> config = new HashMap<>();
        //RandomStringUtils.randomAlphabetic产生10位长度的随机字符串
        //应该是一开始给路由添加acl插件的时候，随便生成一个允许访问的角色
        config.put("allow", Collections.singletonList(RandomStringUtils.randomAlphabetic(10)));
        //都是用内部类
        Plugin acl = new Plugin.Builder("acl")
                .setRoute(new Plugin.Route(route.getId()))
                .setConfig(config).builder();
        Plugin keyAuth = new Plugin.Builder("key-auth")
                .setRoute(new Plugin.Route(route.getId())).builder();

        kongPlugin.addPlugin(acl);
        kongPlugin.addPlugin(keyAuth);
    }

    @Transactional(rollbackFor = {NetContextException.class, IOException.class, URISyntaxException.class})
    public void deleteApiInterfaceRecordDisposable(String id) throws IOException, URISyntaxException {
        apiInterfaceMapper.deleteByPrimaryKey(id);
        //kongRoute.deleteRoute(id);
    }


    public List<ApiSet> getPageSetDetail(Integer pageNo) {
        ApiSetExample example = new ApiSetExample();
        example.setPageInfo(pageNo, pageSize);
        return apiSetMapper.selectByExampleWithBLOBs(example);
    }

    public long getCount() {
        return apiSetMapper.countByExample(null);
    }

    public List<ApiInterfaceWithBLOBs> getListViaService(String serviceId) {
        ApiInterfaceExample example = new ApiInterfaceExample();
        example.or().andServiceIdEqualTo(serviceId);
        return apiInterfaceMapper.selectByExampleWithBLOBs(example);
    }
    //分页条件查询接口集的业务数据
    public PageInfo<ApiSet> findSetPageObject(Integer pageSize, Integer pageCurrent, Integer type, String name) {
        // 1.参数校验
        if(pageCurrent==null||pageCurrent<1)
            throw new IllegalArgumentException("当前页码有误");
        if(pageSize==null||pageSize<1)
            throw new IllegalArgumentException("当前页显示数目有误");
        //按照（数据库）排序字段 倒序 排序
        String orderByStr="date desc";
        //传入查询第几页，以及显示多少条
        PageHelper.startPage(pageCurrent,pageSize,orderByStr);

        List<ApiSet> list=apiSetMapper.findAll(type,name);

        //先看redis中有没有存
        /*if(redisUtils.hasKey(redisServiceKey)){
            //如果存了就从缓存中拿
            list= (List<ApiSet>) redisUtils.get(redisServiceKey);
            logger.info("从缓存中取了数据集数据");
        }else{
            logger.info("从数据库中取了数据集数据");
            //查询记录
            list=apiSetMapper.findAll(type,name);
            redisUtils.set(redisServiceKey,list);
        }*/
        if(list.size()==0){
            throw new AMException(ExceptionEnum.Set_NOT_FOUND);
        }
        //丢给pageInfo处理，拿到总条数，页数，页码等等
        PageInfo<ApiSet> pageInfo=new PageInfo<>(list);
        return pageInfo;
    }

    //查业务数据库的一个接口集的详细数据
    public ApiSet findOneDetailById(String id) {
        ApiSet apiSet=apiSetMapper.findOneDetailById(id);
        if(apiSet==null) {
            throw new AMException(ExceptionEnum.Set_NOT_FOUND);
        }
        return apiSet;
    }

    //跟新业务数据库和kong数据库的service数据
    @Transactional(rollbackFor = {Exception.class})
    public void updateApiSet(ApiSet apiSet, MultipartFile imageFile) throws IOException, URISyntaxException {
        if(StringUtils.isEmpty(apiSet.getId())){
            throw new IllegalArgumentException("要修改的接口集id必须传");
        }

        //从缓存中找到原来的数据集信息
        ApiSet orginSet=null;
        if(redisUtils.hasKey(redisServiceKey)){
            List<ApiSet> listSet= (List<ApiSet>) redisUtils.get(redisServiceKey);
            for(ApiSet temp:listSet){
                if(apiSet.getId().equals(temp.getId())){
                    orginSet=temp;
                    break;
                }
            }
            logger.info("从缓存中拿接口集原来的数据");
        }else{
            orginSet=apiSetMapper.findOneDetailById(apiSet.getId());
            logger.info("从数据库中拿接口集原来的数据");
        }

        //如果新传来的数据图片不为空，就需要把它存入到服务器中
        //有这么一步是因为测试的时候前端就算不上传文件MultipartFile也是不为空的
        /*MultipartFile imageFile = null;
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (isMultipart) {
            MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(request,
                    MultipartHttpServletRequest.class);
            imageFile = multipartRequest.getFile("imageFile");
        }*/
        if(imageFile!=null&&imageFile.getSize()!=0){
            String newPath = SaveFile(imageFile);
            apiSet.setPicUrl(newPath);
            apiSet.setOrignName(imageFile.getOriginalFilename());
        }
        //如果没传图片就不需要存

        //更新业务数据库中的数据
        apiSetMapper.updateById(apiSet);
        //更新了业务数据库中的数据后，需要把缓存中的数据集信息给去掉
        if(redisUtils.hasKey(redisServiceKey)){
            redisUtils.del(redisServiceKey);
            logger.info("修改接口集数据，删除了缓存");
        }


        //如果没有对url进行修改，就不需要改kong数据库
        //修改了才需要改kong数据库
        if(!StringUtils.isEmpty(apiSet.getUrl())&&orginSet!=null&&!orginSet.getUrl().equals(apiSet.getUrl())){
            //更新kong数据库中的数据
            kongService.updateServiceById(apiSet.getId(),apiSet.getUrl());
            logger.info("修改接口集业务数据库，同时修改kong数据库");
        }else{
            logger.info("修改了接口集业务数据库，但没修改kong数据库");
        }
    }

    //查询某个接口集+他的接口的信息
    public ApiSetInterfaces findSetDetailAndChildRoute(String id) {
        ApiSetInterfaces result=apiSetMapper.findSetDetailAndChildRoute(id);
        if(result==null){
            throw new AMException(ExceptionEnum.Set_NOT_FOUND);
        }
        return result;
    }

    //往业务数据库中插入接口信息
    public void addInterface(ApiInterfaceWithBOLOBsPath record,MultipartFile imageFile) throws IOException,
            URISyntaxException {
        String id = UUID.randomUUID().toString();
        record.setId(id);
        //需要将url处理成http://kong服务器地址:8000/代理path/原本的url
        StringBuffer buffer=new StringBuffer(kongNet);
        //就算route中的代理path有多个，这里也默认用它的第一个，后期可以改
        if(!record.getPaths().get(0).startsWith("/")){
            buffer.append("/");
        }
        buffer.append(record.getPaths().get(0));

        if (record.getUrl()!=null && !record.getUrl().equals(""))
        {
            if(!record.getUrl().startsWith("/"))
                buffer.append("/");
        }

        buffer.append(record.getUrl());
        record.setUrl(buffer.toString());
        record.setDate(new Date());
        //这里需要判断传入的json字符串有没有‘’，如果有就需要把'替换为/“
        record.setRequestParameters(replaceDYH(record.getRequestParameters()));
        if (record.getResponseParameters()!=null)
        {
            record.setResponseParameters(replaceDYH(record.getResponseParameters()));
        }
        record.setResponseExample(replaceDYH(record.getResponseExample()));

        //响应参数图片处理
        if (imageFile!=null)
        {
            String responsePicUrl = SaveFile(imageFile);
            record.setResponse_pic_url(responsePicUrl);
        }

        //往业务数据库插入
        apiInterfaceMapper.insert(record);
    }
    public String replaceDYH(String str){
        String temp= null;
        if(!StringUtils.isEmpty(str)){
            temp=str.replaceAll("'","\\\\\"");
        }
        return temp;
    }

    public ApiInterfaceWithBLOBs getInterfaceById(String id) {
        ApiInterfaceWithBLOBs apiInterfaceWithBLOBs = apiInterfaceMapper.selectByPrimaryKey(id);
        if(apiInterfaceWithBLOBs==null){
            throw new AMException(ExceptionEnum.Interface_NOT_FOUND);
        }
        return apiInterfaceWithBLOBs;
    }

    public List<Route> listRoutesByServiceId(String id) throws IOException, URISyntaxException {
        List<Route> result = kongService.listRoutesByServiceId(id);
        return result;
    }

    public void updateInterface(ApiInterfaceWithBOLOBsPath record,
                                MultipartFile imageFile) {
        //除了id不能为空其他都可以为空
        if(StringUtils.isEmpty(record.getId())){
            throw new AMException(ExceptionEnum.ID_IS_NULL);
        }
        if(record.getPaths()==null||record.getPaths().size()<1){
            throw new AMException(ExceptionEnum.PATH_IS_NULL);
        }
        //这里需要判断传入的json字符串有没有‘’，如果有就需要把'替换为/“
        record.setRequestParameters(replaceDYH(record.getRequestParameters()));
        if (record.getResponseParameters()!=null)
        {
            record.setResponseParameters(replaceDYH(record.getResponseParameters()));
        }
        record.setResponseExample(replaceDYH(record.getResponseExample()));

        //如果有传url
        //需要将url处理成http://kong服务器地址:8000/代理path/原本的url
        StringBuffer buffer=new StringBuffer(kongNet);
        //就算route中的代理path有多个，这里也默认用它的第一个，后期可以改
        if(!record.getPaths().get(0).startsWith("/")){
            buffer.append("/");
        }
        buffer.append(record.getPaths().get(0));
        if (record.getUrl()!=null && !record.getUrl().equals(""))
        {
            if(!record.getUrl().startsWith("/"))
                buffer.append("/");
        }

        buffer.append(record.getUrl());
        record.setUrl(buffer.toString());

        //响应参数图片处理
        if (imageFile!=null)
        {
            String responsePicUrl = SaveFile(imageFile);
            record.setResponse_pic_url(responsePicUrl);
        }

        //更新业务数据库
        apiInterfaceMapper.update(record);
    }
}
